/***************************************************************************
 *   oboe.resources.Manager                                                *
 *   Copyright (C) 2011 by Felipe Manga                                    *
 *   lufeboma@gmail.com                                                    *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

package oboe.resources
{
//	import banjo3D.parsers.Parser;
	
	import com.adobe.protocols.dict.events.ErrorEvent;
	import com.adobe.serialization.json.JSON;
	
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.media.Sound;
	import flash.media.SoundLoaderContext;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.system.Security;
	import flash.system.SecurityDomain;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.setTimeout;
	
	import oboe.core.Oboe;
	import oboe.math.Mesh3D;
	import oboe.resources.zip.ZipEntry;
	import oboe.resources.zip.ZipFile;
	import oboe.utils.MD2;
	import oboe.utils.MD2Parser;
	import oboe.utils.audiofx.mp3.MP3FileReferenceLoader;
	import oboe.utils.audiofx.mp3.MP3SoundEvent;
	

	/**
	 * Singleton class. Loads resources (images/models/swfs/zips/etc) and calls their respective initialization routines based on file extension. 
	 * @author FelipeManga
	 * 
	 */
	public class Manager
	{
		private static var _instance : Manager;
		private var queue : Dictionary;
		private var outData : Array;
		private var PROJECT_ROOT : String;
		
		/**
		 * 
		 * @return a reference to the instance of this class. If there is none, one is created and stored. 
		 * 
		 */
		public static function get instance() : Manager {
			if( !_instance ) _instance = new Manager();
			return _instance;
		}
		
		public function Manager()
		{
			if( _instance ) throw new Error( "onrock.resources.Manager REINSTANCIATION");
			this.PROJECT_ROOT = Oboe.flags["RESOURCES"];
		}
		
		private static function getExt(str : String):String{
			return str.substr( str.lastIndexOf(".") + 1 ).toLowerCase();
		}
		
		private function decodeFile( data : Object, qe : QueueElement ) : void {
			var out : Object;
			switch( qe.extension ){
			case "json":
				if( data is ByteArray ) data = data.toString();
				out = JSON.decode( data as String );
				break;
/* * / // Banjo3D
			case "pms":
			case "pmz":
			case "pmzl":
			case "md2":
				out = Parser.run( data as ByteArray, qe.extension );
				break;
/*/	// Oboe
			case "md2":
				qe.bytes = data as ByteArray;
				makeParser( qe );
				out = null;
				break;
/* */
			case "png":
			case "jpg":
			case "jpeg":
			case "swf":
			case "gif":
			case "mp3":
				makeLoader( qe );
				out = null;
				break;
			case "plist":
				if( data is ByteArray ) data = data.toString();
				out = PlistParser.run( String(data) );
				break;
			case "txt":
				if( data is ByteArray ) data = data.toString();
				out = data;
				break;
			case "xml":
				out = new XML( data );
				break;
			case "zip":
				var zip : ZipFile = new ZipFile( data );
				var d : Object = { filesLeft:zip.entries.length, queueElement:qe };
				for( var i:int = 0; i<zip.entries.length; ++i ){
					var entry : ZipEntry = zip.entries[i];
					var nqe : QueueElement = new QueueElement( qe.url+"/"+entry.name, zipDone, d, getExt(entry.name) ); 
					instance.queue[nqe] = nqe;
					nqe.bytes = zip.getInput( entry );
					// decodeFile( nqe.bytes, nqe );
					if( Oboe.flags["SLOW_LOAD"] )
					{
						setTimeout( decodeFile, 10+Math.random()*500, nqe.bytes, nqe );
					}	
					else
					{
						decodeFile( nqe.bytes, nqe );
					}
					
					out = null;
				}
				break;
			default:
				out = data;
				break;
			}
			if( out != null ) fileReady( out, qe );
		}
		
		private function zipDone( obj : Object ) : void {
			obj.extra.filesLeft--;
			Files.registerResource( obj.url, obj.object, obj.appDomain );
			if( obj.extra.filesLeft <= 0 ){
				fileReady( null, obj.extra.queueElement );
			}
		}
		
		private function fileReady( data:Object, qe : QueueElement ) : void {
			if( qe.callback != null )
			{
				qe.callback( { object: data, url: qe.url, appDomain: qe.appDomain, extra:qe.extra } );
			}
		}
		
		private function downloadNext( e : Event, currentData : QueueElement ) : void {
			var data : Object;
			if( e.target is LoaderInfo ){
				if( Oboe.flags["DEBUG"] ) trace( "[FILE] ", currentData.url, " - transfer complete" );
				fileReady( (e.target as LoaderInfo).content, currentData );
			}else if( e.target is Sound ){
				if( Oboe.flags["DEBUG"] ) trace( "[FILE] ", currentData.url, " - transfer complete" );
				fileReady( e.target, currentData );
			}else if( e is MP3SoundEvent ){
				if( Oboe.flags["DEBUG"] ) trace( "[FILE] ", currentData.url, " - transfer complete" );
				fileReady( MP3SoundEvent(e).sound, currentData );
			}else if( e.target is MD2Parser ){
				if( Oboe.flags["DEBUG"] ) trace( "[FILE] ", currentData.url, " - parsing complete" );
				fileReady( e.target.data, currentData );
			}else{
				if( Oboe.flags["DEBUG"] ) trace( "[FILE] ", currentData.url, " - transfer complete" );
				decodeFile( (e.target as URLLoader).data, currentData );
			}
			delete instance.queue[ currentData ];
		}
		
		private static function makeParser( qe : QueueElement ):void
		{
			var cb : Function =	function ( e : Event ) : void{ 
				(e.target as EventDispatcher).removeEventListener(e.type, arguments.callee);
				_instance.downloadNext(e, nqe); 
			};
			var nqe:QueueElement = new QueueElement( qe.url, qe.callback, qe.extra, qe.extension );
			nqe.ldr = new MD2Parser( qe.bytes );
			nqe.ldr.addEventListener( Event.COMPLETE, cb );
			instance.queue[nqe] = nqe;
		}
		
		private static function error( qe:QueueElement ):void
		{
			if( Oboe.flags["DEBUG"] ) 
				trace( "Error loading: ", qe.url );
			if( qe.callback != null )
			{
				qe.callback( { object: null, url: qe.url, appDomain: qe.appDomain, extra:qe.extra }, false );
			}
		}
		
		private static function makeLoader( qe : QueueElement ) : void {
			var req : URLRequest;
			if( !qe.bytes )
			{
				if( Oboe.flags.DEBUG && Oboe.flags.BYPASS_CACHE )
				{
					var bpurl:String = qe.url;
					bpurl += bpurl.indexOf("?") > -1 ? "&" : "?";
					bpurl += "rndCacheBypass=" + int( Math.random()*0xFFFF );
					req = new URLRequest( bpurl );
				}
				else
				{
					req = new URLRequest( qe.url );
				}
			}
			var cb : Function =	function ( e : Event ) : void{ 
				(e.target as EventDispatcher).removeEventListener(e.type, arguments.callee);
				_instance.downloadNext(e, qe); 
			};
			var swfdata : LoaderContext = null;
			switch(qe.extension){
				case "swf": 
					qe.appDomain = new ApplicationDomain( /* ApplicationDomain.currentDomain */	 );
					swfdata = new LoaderContext( false, qe.appDomain );
				case "png":
				case "jpg":
				case "jpeg":
				case "bmp":
				case "gif":
					qe.ldr = new Loader();
					qe.ldr.contentLoaderInfo.addEventListener( Event.COMPLETE, cb );
					qe.ldr.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, function ( e : Event ) : void { error(qe); });
					qe.ldr.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, 
					function(e:ErrorEvent):void{
						e.stopPropagation();
						error(qe);
					}, false, 0, true);

					if( req )
					{
						(qe.ldr as Loader).load( req, swfdata );
					}else{
						(qe.ldr as Loader).loadBytes( qe.bytes, swfdata );
					} 
					
					break;
				case "mp3":
					var context:SoundLoaderContext = new SoundLoaderContext(8000, false);
					if( req )
					{
						qe.ldr = new Sound( req, context );
						Sound(qe.ldr).addEventListener( Event.COMPLETE, cb );
						qe.ldr.addEventListener( IOErrorEvent.IO_ERROR, function ( e : Event ) : void { error(qe); });
						if( !Oboe.flags["WAIT_FOR_MP3"] )
						{
							(qe.ldr as Sound).dispatchEvent( new Event(Event.COMPLETE) );
						}
					}
					else
					{
						qe.ldr = new MP3FileReferenceLoader();
						MP3FileReferenceLoader(qe.ldr).getSoundFromByteArray( qe.bytes );
						qe.ldr.addEventListener( Event.COMPLETE, cb );
						qe.ldr.addEventListener( IOErrorEvent.IO_ERROR, function ( e : Event ) : void { error(qe); });
					}
					break;
				case "txt":
				case "json":
				case "xml":
				case "ini":
				case "plist":
					qe.ldr = new URLLoader();
					(qe.ldr as URLLoader).dataFormat = flash.net.URLLoaderDataFormat.TEXT;
					qe.ldr.addEventListener( IOErrorEvent.IO_ERROR, function ( e : Event ) : void { error(qe); });
					qe.ldr.addEventListener(SecurityErrorEvent.SECURITY_ERROR, 
						function(e:ErrorEvent):void{
							e.stopPropagation();
							error(qe);
						}, false, 0, true);
					qe.ldr.addEventListener( Event.COMPLETE, cb );
					qe.ldr.load( req );
					break;
				case "pms":
				case "pmz":
				case "pmzl":
				case "bin":
				case "md2":
				case "zip":
				default:
					qe.ldr = new URLLoader();
					qe.ldr.dataFormat = flash.net.URLLoaderDataFormat.BINARY;
					qe.ldr.addEventListener( Event.COMPLETE, cb );
					qe.ldr.addEventListener( IOErrorEvent.IO_ERROR, function ( e : Event ) : void { error(qe); });
					qe.ldr.load( req );
					break;
			}
		}
		
		public static function getFromClass( c:Class, ext:String, callback : Function = null ):void
		{
			if( !instance.queue ) instance.queue = new Dictionary();
			var qe : QueueElement = new QueueElement(
				null,
				callback,
				null,
				ext
			);
			
			_instance.decodeFile( new c(), qe );
			
		}
		
		/**
		 * Adds a file to the loading queue. 
		 * @param link The address of the file to load. If this URL does not start with "http:", the file is loaded relative to the PROJECT_ROOT/resources/ folder.
		 * @param callback The function to call when this file is done loading.
		 * @param extraData generic reference to data that should be returned along with the file.
		 * 
		 */
		public static function getURL( link : String, callback : Function = null, extraData : * = null ) : void {
			var ext : String = getExt(link);
			if( !instance.queue ) instance.queue = new Dictionary();
			var fullurl : String = link;
			
			if( fullurl.substr(0, 6).toLowerCase() == "alias:" )
			{
				fullurl = fullurl.substr( fullurl.indexOf("@")+1 );
			}
			
			if( fullurl.substr(0, 4 ).toLowerCase() == "rel:" )
			{
				fullurl = fullurl.substr(4);
			}
			else if( fullurl.substr(0, 5).toLowerCase() != "http:" )
			{
				fullurl = instance.PROJECT_ROOT + fullurl; 
			}
			
			var qe : QueueElement = new QueueElement(
				fullurl,
				callback,
				extraData,
				ext
			);
			
			instance.queue[ qe ] = qe;
			
			makeLoader( qe );
		}
	}
}

import flash.media.Sound;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.utils.ByteArray;

class QueueElement {
	public var handle: String;
	public var url : String;
	public var callback: Function;
	public var ldr: *;
	public var extra: *;
	public var extension : String;
	public var appDomain : ApplicationDomain;
	public var bytes : ByteArray;
	public function QueueElement( 
		url:String, 
		callback:Function,
		extra: *, 
		extension : String 
	){
		this.url = url;
		this.callback = callback;
		this.extra = extra;
		this.extension = extension;
	}
}
